package com.linkoog.devtools.code_generator.wizard.model;

import com.linkoog.devtools.code_generator.common.JavaType;
import com.linkoog.devtools.code_generator.util.PsiFileUtil;
import com.linkoog.devtools.code_generator.util.StringUtil;
import com.linkoog.devtools.intellij.wizard.WizardContext;
import com.linkoog.devtools.intellij.wizard.WizardStep;
import com.linkoog.devtools.code_generator.wizard.ModelWizardContext;
import com.intellij.ide.util.PackageChooserDialog;
import com.intellij.notification.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiPackage;
import com.intellij.psi.search.GlobalSearchScope;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.awt.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;
import java.util.function.Consumer;

public class GenModelFieldStep extends WizardStep {

    private static NotificationGroup notificationGroup;
    static {
        notificationGroup = new NotificationGroup("DevTools.CodeGen",  NotificationDisplayType.BALLOON, false);
    }

    private Project project;
    private Module module;
    private VirtualFile selectedPackageVF;
    private PsiDirectory basePsiDirectory;

    private JPanel myMainPanel;
    private JTable fieldsTable;

    private JTextField packageTextField;
    private JButton packageSelectButton;
    private JTextField classNameTextField;
    private JTextField classRemarkTextField;
    private JCheckBox lombokCheckBox;
    private JCheckBox swaggerCheckBox;
    private JCheckBox mybatisplusCheckBox;


    public GenModelFieldStep(Project project, WizardContext wizardContext, Module module, VirtualFile selectedPackageVF) {
        super(project, wizardContext);

        this.project = project;
        this.module = module;
        this.selectedPackageVF = selectedPackageVF;
        initBasePackage(project, selectedPackageVF);
        lombokCheckBox.setSelected(true);
        swaggerCheckBox.setSelected(true);
        this.myMainPanel.setMinimumSize(new Dimension(770, 550));
    }

    private void initBasePackage(@NotNull Project project, @NotNull VirtualFile selectedPackageVF) {
        ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
        String selectedPackageName = projectFileIndex.getPackageNameByDirectory(selectedPackageVF);
        if (StringUtils.isBlank(selectedPackageName)) {
            selectedPackageName = "<default>";
        }

        this.selectedPackageVF = selectedPackageVF;
        this.basePsiDirectory = PsiManager.getInstance(project).findDirectory(this.selectedPackageVF);

        packageTextField.setText(selectedPackageName);
        packageTextField.setEditable(false);
        packageSelectButton.addActionListener(e -> {
            chooserPackage(packageTextField.getText(), psiPackage -> {
                PsiDirectory[] psiDirectories = psiPackage.getDirectories(GlobalSearchScope.moduleRuntimeScope(this.module,false));
                if (psiDirectories.length == 1){
                    this.basePsiDirectory = psiDirectories[0];
                    packageTextField.setText(psiPackage.getQualifiedName());
                }
            });
        });
    }

    /**
     * 选择包名
     *
     * @param defaultSelect 默认选中包名
     * @param consumer      完成事件
     */
    private void chooserPackage(String defaultSelect, Consumer<PsiPackage> consumer) {
        PackageChooserDialog chooser = new PackageChooserDialog("请选择package包", module);
        chooser.selectPackage(defaultSelect);
        chooser.show();
        PsiPackage psiPackage = chooser.getSelectedPackage();
        if (psiPackage != null) {
            consumer.accept(psiPackage);
        }
        chooser.getDisposable().dispose();
    }

    @Override
    public void onStepPrepare(){
        ModelWizardContext modelWizardContext = (ModelWizardContext) getWizardContext();
        if (StringUtils.isNotBlank(modelWizardContext.getClassName())){
            classNameTextField.setText(modelWizardContext.getClassName());
        }

        if (StringUtils.isNotBlank(modelWizardContext.getClassRemark())){
            classRemarkTextField.setText(modelWizardContext.getClassRemark());
        }

        getFieldsTable().setModel(modelWizardContext.getRoot());
    }

    @Override
    public boolean validate() throws ConfigurationException {
        if (StringUtils.isBlank(classNameTextField.getText())){
            throw new ConfigurationException("请输入类名！", "错误");
        }
        return true;
    }

    @Override
    public void onStepOK() {
        String modelName = handlerFristChar(classNameTextField.getText(), true);

        ApplicationManager.getApplication().runReadAction(()  -> {
            String context = buildModelContext();
            PsiFileUtil.CreateFileParam createFileParam = new PsiFileUtil.CreateFileParam(null, modelName + ".java", context);
            try {
                PsiFileUtil.createFile(project, basePsiDirectory, createFileParam);
                SwingUtilities.invokeLater(() -> showNotityMsg("创建成功!<br/>"));
            } catch (Exception e) {
                SwingUtilities.invokeLater(() -> showNotityMsg("创建失败：<br/>" + e.getMessage()));
            }
        });
    }


    private String buildModelContext(){
        StringBuilder builder = new StringBuilder();
        buildImportPart(builder);
        buildClassRemark(builder);
        buildClassContext(builder);

        return builder.toString();
    }

    private void buildImportPart(StringBuilder builder) {
        String packageName = packageTextField.getText();
        if (! "<default>".equals(packageName) ){
            builder.append("package ").append(packageName).append(";\n");
            builder.append("\n\n");
        }


        builder.append("import java.io.Serializable;\n");
        if (lombokCheckBox.isSelected()){
            builder.append("import lombok.Data;\n");
        }

        if (swaggerCheckBox.isSelected()){
            builder.append("import io.swagger.annotations.ApiModel;\n");
            builder.append("import io.swagger.annotations.ApiModelProperty;\n");
        }

        if (mybatisplusCheckBox.isSelected()){
            builder.append("import com.baomidou.mybatisplus.annotation.TableName;\n");
            builder.append("import com.baomidou.mybatisplus.annotation.TableField;\n");
        }
        builder.append("\n\n");
    }


    private void buildClassRemark(StringBuilder builder){
        String modelRemark = classRemarkTextField.getText();
        builder.append("/**\n")
                .append(" *").append(modelRemark).append("\n")
                .append(" *\n")
                .append(" * @author ").append(System.getProperty("user.name")).append("\n")
                .append(" * @date ").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())).append("\n")
                //.append(" * @remark ").append(modelRemark).append("\n")
                .append(" */\n");
    }

    private void buildClassContext(StringBuilder builder){
        String modelName = handlerFristChar(classNameTextField.getText(), true);
        if (lombokCheckBox.isSelected()){
            builder.append("@Data\n");
        }
        if (swaggerCheckBox.isSelected()){
            builder.append("@ApiModel(value = \"").append(modelName).append("\")\n");
        }
        if (mybatisplusCheckBox.isSelected()){
            builder.append("@TableName(value = \"").append(StringUtil.textToSnakeCase(modelName)).append("\")\n");
        }

        ModelJsonTreeTable.ItemInfo root = getFieldsTable().getRoot();
        builder.append("public class ").append(modelName).append(" implements Serializable{\n");
        builder.append("\n\n");
        builder.append(buildClassBody(root, 1));
        builder.append("}\n");
    }

    private String buildClassBody(ModelJsonTreeTable.ItemInfo root, int level){
        List<String> fields = new ArrayList<>();
        Map<String,String> methods = new LinkedHashMap<>();
        List<String> innerClazzs = new ArrayList<>();

        for (int i = 0; i < root.getChildCount(); i++) {
            ModelJsonTreeTable.ItemInfo childNode =(ModelJsonTreeTable.ItemInfo)root.getChildAt(i);
            String javaTypeClass = parseTypeName(childNode);
            fields.add(buildField(childNode, javaTypeClass, level));
            if (! lombokCheckBox.isSelected()){
                methods.put(buildGetterMethod(childNode, javaTypeClass, level), buildSetterMethod(childNode, javaTypeClass, level));
            }

            if (childNode.getType() == JavaType.OBJECT){
                innerClazzs.add(buildInnerClass(childNode, level));
            }
        }

        StringBuilder builder = new StringBuilder();
        for (String field : fields) {
            builder.append(field);
            builder.append("\n");
        }

        for (String getterMethod : methods.keySet()) {
            String setterMethod = methods.get(getterMethod);
            builder.append(getterMethod);
            builder.append("\n");

            builder.append(setterMethod);
            builder.append("\n");
        }

        for (String innerClazz : innerClazzs) {
            builder.append(innerClazz);
            builder.append("\n");
        }

        return builder.toString();
    }


    private String buildBlackSpace(int level){
        String temp = "";
        for (int i = 1; i <= level; i++) {
            temp += "    ";
        }
        return temp;
    }

    private String buildField(ModelJsonTreeTable.ItemInfo itemInfo, String javaTypeClass, int level){
        StringBuilder builder = new StringBuilder();
        if(StringUtils.isNotBlank(itemInfo.getRemark())){
            builder.append(buildBlackSpace(level)).append("/**\n")
                    .append(buildBlackSpace(level)).append("*").append(itemInfo.getRemark()).append("\n")
                    .append(buildBlackSpace(level)).append("*/\n");
        }
        if (swaggerCheckBox.isSelected()){
            builder.append(buildBlackSpace(level)).append("@ApiModelProperty(value = \"").append(itemInfo.getRemark()).append("\")\n");
        }
        if (mybatisplusCheckBox.isSelected()){
            //添加mybatis-plus的注解
            //builder.append(buildBlackSpace(level)).append("@TableField(value = \"").append(itemInfo.getRemark()).append("\")\n");
        }
        builder.append(buildBlackSpace(level)).append("private " + javaTypeClass + " " + itemInfo.getName() + ";\n");
        return builder.toString();
    }

    private String buildGetterMethod(ModelJsonTreeTable.ItemInfo itemInfo, String javaTypeClass, int level){
        String ModelName = handlerFristChar(itemInfo.getName(), true);
        StringBuilder getter = new StringBuilder();
        getter.append(buildBlackSpace(level)).append("public ").append(javaTypeClass).append(" ");
        if (itemInfo.getType() == JavaType.BOOLEAN){
            getter.append("is").append(ModelName).append("(){\n");
        } else {
            getter.append("get").append(ModelName).append("(){\n");
        }
        getter.append(buildBlackSpace(level+1)).append("return ").append(itemInfo.getName()).append(";\n");
        getter.append(buildBlackSpace(level)).append("}\n");
        return getter.toString();
    }

    private String buildSetterMethod(ModelJsonTreeTable.ItemInfo itemInfo, String javaTypeClass, int level){
        String ModelName = handlerFristChar(itemInfo.getName(), true);
        StringBuilder setter = new StringBuilder();
        setter.append(buildBlackSpace(level)).append("public void ").append("set").append(ModelName).append("(").append(javaTypeClass).append(" ").append(itemInfo.getName()).append("){\n");
        setter.append(buildBlackSpace(level+1)).append("this.").append(itemInfo.getName()).append(" = ").append(itemInfo.getName()).append(";\n");
        setter.append(buildBlackSpace(level)).append("}\n");
        return setter.toString();
    }


    private String buildInnerClass(ModelJsonTreeTable.ItemInfo root, int level){
        StringBuilder builder = new StringBuilder();
        String modelName = handlerFristChar(root.getName(), true);
        if (lombokCheckBox.isSelected()){
            builder.append(buildBlackSpace(level)).append("@Data\n");
        }
        if (swaggerCheckBox.isSelected()){
            builder.append(buildBlackSpace(level)).append("@ApiModel(value = \"").append(modelName).append("\")\n");
        }

        builder.append(buildBlackSpace(level)).append("public static class ").append(modelName).append(" implements Serializable{\n");
        builder.append("\n\n");
        builder.append(buildClassBody(root,level + 1));
        builder.append(buildBlackSpace(level)).append("}\n");
        return builder.toString();
    }


    private String parseTypeName(ModelJsonTreeTable.ItemInfo itemInfo){
        if (itemInfo.getType() == JavaType.OBJECT){
            return handlerFristChar(itemInfo.getName(), true);
        } else if (itemInfo.getType() == JavaType.LIST){
            ModelJsonTreeTable.ItemInfo childNode = getItemsNode(itemInfo);
            String javaTypeClass = JavaType.LIST.getFullName();
            if (childNode == null){
                return javaTypeClass;
            }else {
                return javaTypeClass +  "<" +  parseTypeName(childNode) + ">";
            }
        } else {
            JavaType type = itemInfo.getType();
            return type.getFullName() != null ? type.getFullName() : type.getName();
        }
    }

    private ModelJsonTreeTable.ItemInfo getItemsNode(ModelJsonTreeTable.ItemInfo itemInfo){
        while (itemInfo.getChildCount() > 0){
            ModelJsonTreeTable.ItemInfo childNode = (ModelJsonTreeTable.ItemInfo)itemInfo.getChildAt(0);
            if ("Items".equals(childNode.getName())){
                return childNode;
            }
        }
        return null;
    }

    public ModelJsonTreeTable getFieldsTable(){
        return (ModelJsonTreeTable)fieldsTable;
    }

    private static String handlerFristChar(String model, boolean upperCase) {
        model = model.trim();
        if (model.length() == 0) {
            return "";
        } else if (model.length() == 1) {
            return upperCase ? model.toUpperCase() : model.toLowerCase();
        } else {
            String fristChar = model.substring(0, 1);
            if (upperCase){
                return fristChar.toUpperCase() + model.substring(1);
            }
            return fristChar.toLowerCase() + model.substring(1);
        }
    }

    private void showNotityMsg(String errorMsg) {
        Notification error = notificationGroup.createNotification(errorMsg, NotificationType.INFORMATION);
        Notifications.Bus.notifyAndHide(error, project);
    }

    @Override
    public JPanel getComponent() {
        return myMainPanel;
    }

    private void createUIComponents() {
        fieldsTable = new ModelJsonTreeTable();
    }
}
